/*
 * license.c - CMDB License Control Module
 * Clunix, Yongjae Lee <yjlee@clunix.com>, 2000.10.17,2001.1.9,11.1.
 *
 * Copyright (c) 2000 by Clunix Inc. All Rights Reserved.
 *
 * If this file is compiled with -DKEYGEN, the license key generator is created.
 */

/*
 * DOC
 * license_key_format.txt - ECM-1.0 license key format
 * Clunix, Yongjae Lee <yjlee@clunix.com>, 2001.1.9.
 *
 * char license_key[36] ::= asciiz string of:
 *
 *   ECM10-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
 *   <p1>  <p2>  <p3>  <p4>  <p5>  <p6>
 *
 *   <p1>: LICENSE_KEY_PREFIX (also product symbol; should be uppercase);
 *         readable and recognizable format;
 *         Use the same alphabet as <p2>-<p6>
 *         ECM10 is used
 *   <p2>-<p6>: Base32-encoded string of encrypted key;
 *              Composed of 1-9 and A-Z;
 *              When read, lowercase chars are changed to upper case;
 *              When written, uppercase only
 *
 * unsigned char encoded_key[25] ::=
 *   concat(<p1>, <p2>, <p3>, <p4>, <p5>, <p6>) ::=
 *   struct {
 *     char license_key_prefix;
 *     char base32_encoded_key[24];
 *     char license_key_checksum;
 *   };
 *
 *   license_key_checksum ::= hexadecimal representation of 
 *     (sum(license_key_prefix + base32_encoded_key + '-' * 5) *
 *      LICENSE_KEY_C1(=77)) % 16;
 *
 * unsigned char encrypted_key[15] ::=
 *   base32 decoded base32_encoded_key[];
 *
 * unsigned char raw_key[15] ::=
 *   decrypted encrypted_key[] ::=
 *   struct {
 *     unsigned char product_key[14];
 *     unsigned char raw_key_checksum;
 *   }
 *
 *   raw_key_checksum ::= (sum(product_key[14]) * LICENSE_KEY_C2(=301)) % 256;
 *
 * unsigned char product_key[14] ::=
 *   {
 *     unsigned char product_symbol[5] = { 'E', 'N', 'C', '1', '6' };
 *        the same as LICENSE_PREFIX 
 *     unsigned char product_arch[1] =
 *        { 'x' for any, 'i' for i386, 'a' for alpha or 't' for ia64
 *          's' for sparc, 'p' for powerpc, 'm' for mips }
 *     short node_limit;
 *     int expire_time;
 *        for limited license, (expire_time % 60) should be 0 
 *        for unlimited license, (expire_time % 60) should be 1 and
 *          expire_time itself is random value 
 *     short random_key;   random value 
 *     unsigned char char checksum;
 *   };
 *
 *   In product_key(8*14=112bits), the free variable space should be smaller
 *   than 8 byte long (=8*8=64bits)
 *
 * EOD
 */

#include "config.h"

#ifndef WIN32
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#else
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "ecm.h"
#include "license.h"

extern const char	*edbd_clxdir;

extern char *ecm_decrypt(const char *encdata, ecm_size_t len);

#ifndef KEYGEN

#define LICENSE_KEY_FILE	"license.key"

#if HIDDEN_KEY_CHECK
#define HIDDEN_KEY  "\232\300\306\307\232\331\334\327\232\326\321\330\320\233\326\323\322"
#endif

static int license_expire = 0;  /* 0 means no limit */
static int license_node = 0;    /* 0 means no limit */
static int license_level = LICENSE_LEVEL_NONE;

#endif

/*
 * License Key Prefix Utilities ---------------------------------------------
 */

int
get_license_level(char *prefix)
{
	if (prefix) {
		if (!strncmp(prefix, "ECM", 3))
			return LICENSE_LEVEL_FULL;
		//else if (!strncmp(prefix, "ENB", 3))
			//return LICENSE_LEVEL_BASE;
	}
	return LICENSE_LEVEL_NONE;
}

char *
get_license_level_str(int level)
{
	return ((level == LICENSE_LEVEL_NONE)? "none" :
			(level == LICENSE_LEVEL_BASE)? "base" :
			(level == LICENSE_LEVEL_FULL)? "full" : "unknown");
}

void
get_license_arch_str(int arch, char *arch_str_ret)
{
	switch(arch) {
	case 'x': strcpy(arch_str_ret, "any");   break;
	case 'i': strcpy(arch_str_ret, "i386");  break;
	case 'a': strcpy(arch_str_ret, "alpha"); break;
	case 't': strcpy(arch_str_ret, "ia64");   break;
	default:  sprintf(arch_str_ret, "unknown(%c)", arch);
	}
}

/*
 * Base32 encoding/decoding module -----------------------------------------
 * From: base32.c - Glory's base32 encoding module
 * Clunix, Glory Park <glory@clunix.com>, 2001.1.9.
 * From: cmdb-1.2.0/cm/base64.c
 */

static int decodeMap[256] = {
	/* 0x00 */
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	22, 23, 24, 25, 26, 27, 28, 29,  30, 31,  0,  0,  0,  0,  0,  0,
	/* 0x40 */
	 0,  0,  1,  2,  3,  4,  5,  6,   7,  0,  8,  9, 10, 11,  0, 12,
	13, 14, 15, 16, 17,  0, 18, 19,   0, 20, 21,  0,  0,  0,  0,  0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	/* 0x80 */
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	/* 0xc0 */
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,    
};

char *
base32_decode(char *_enc, int enc_len, int *org_len)
{
	unsigned char *enc, *org, *o;
	int n, i, x1, x2;

	/* returns dynamically allocated buffer */

	/* Check if valid input array */
	if (_enc == NULL || enc_len < 0 || enc_len % 8) {
		*org_len = 0;
		return NULL;
	}

	enc = (unsigned char *)_enc;
	n = enc_len;

	/* Allocate org buffer */
	*org_len = (n / 8) * 5;
	org = (unsigned char *)ecm_malloc(*org_len);

	o = org;
	for (i = 0; i < n; i += 8) {
		x1 = ((decodeMap[enc[i]] << 20) + (decodeMap[enc[i+1]] << 15) +
				(decodeMap[enc[i+2]] << 10) + (decodeMap[enc[i+3]] << 5) +
				(decodeMap[enc[i+4]]));
		o[0] = (unsigned char)((x1 >> 17) & 0xff);
		o[1] = (unsigned char)((x1 >>  9) & 0xff);
		o[2] = (unsigned char)((x1 >>  1) & 0xff);

		x2 = ((decodeMap[enc[i+4]] << 15) + (decodeMap[enc[i+5]] << 10) +
				(decodeMap[enc[i+6]] << 5) + (decodeMap[enc[i+7]]));

		o[3] = (unsigned char)((x2 >> 8) & 0xff);
		o[4] = (unsigned char)(x2 & 0xff);
		o += 5;
	}

	return (char *)org;
}


/*
 * License Key String Encoding/Decoding Module ------------------------------
 */

/*
 * license_key_to_product_key()
 * Decode license key string to raw key data
 */

int
license_key_to_product_key(char *_license_key, char *product_key_buf)
{
#ifndef WIN32
	char license_key[36], base32_encoded_key[24 + 1], tmp[4];
#else
	char license_key[36], base32_encoded_key[24 + 2], tmp[4];
#endif
	char *encrypted_key, *raw_key;
	int i, j, c, license_key_checksum, raw_key_checksum;

	if (_license_key == NULL) return -1;
	if (strlen(_license_key) != 35) return -1;  /* invalid key length */

	/* fold to upper case */
	strcpy(license_key, _license_key);
	for (i = 0; i < 35; i++)
		license_key[i] = toupper(license_key[i]);
	license_key[35] = 0;

	/* check format */
	license_key_checksum = 0;
	for (i = 0; i < 6; i++)
		license_key_checksum += (unsigned char)license_key[i];
	for (i = 6; i < 34; i++) {
		c = license_key[i];
		if (i % 6 == 5) {
			if (c != '-')
				break;
		}
		else {
			if (!((c >= '0' && c <= '9') ||
				(c >= 'A' && c <= 'Z' &&
				c != 'U' && c != 'N' && c != 'I' && c != 'X')))
				break;
		}
		license_key_checksum += (unsigned char)c;
	}
	if (i != 34)
		return -1;  /* invalid format */

	/* check license_key_checksum */
	sprintf(tmp, "%X", (license_key_checksum * LICENSE_KEY_C1) % 16);
	if (license_key[34] != tmp[0])
		return -1;  /* invalid checksum */

	/* remove dash and get base32_encoded_key */
	j = 0;
	for (i = 6; license_key[i] != 0; i++)
		if (license_key[i] != '-') {
			base32_encoded_key[j] = license_key[i];
			j++;
		} 
	base32_encoded_key[j] = 0;  /* mark string end */ 

	/* decode base32 */
	if ((encrypted_key = base32_decode(base32_encoded_key, 24, &i)) == NULL)
		return -1;  /* base32 decode error */
	if (i != 15) {
		ecm_free(encrypted_key);
		return -1;  /* base32 decode error */
	}

	/* decrypt */
	raw_key = ecm_decrypt(encrypted_key, 15);
	ecm_free(encrypted_key);
	if (raw_key == NULL)
		return -1;

	/* check raw_key_checksum */
	raw_key_checksum = 0;
	for (i = 0; i < 14; i++)
		raw_key_checksum += (unsigned char)raw_key[i];
	if ((unsigned char)raw_key[14] != (raw_key_checksum * LICENSE_KEY_C2) % 256) {
		ecm_free(raw_key);
		return -1;  /* raw_key_checksum error */
	}

	/* copy result */
	memcpy(product_key_buf, raw_key, 14);
	ecm_free(raw_key);

	return 0;
}

/*
 * product_key_to_license_key()
 * Encode raw key data to license key string
 */

/*
 * Product Specific License Key Module --------------------------------------
 * License encoding and decoding of ECM-1.0 (EnCluster-1.6, EnCluster-1.2 compatible)
 */

void
swap_bit(char *buf, int a, int b)
{
	int byte_a, byte_b, mask_a, mask_b;

	byte_a = (unsigned char)buf[a >> 3];
	byte_b = (unsigned char)buf[b >> 3];
	mask_a = 1 << (a & 7);
	mask_b = 1 << (b & 7);
	buf[a >> 3] = (byte_a & (~mask_a)) | ((byte_b & mask_b)? mask_a : 0);
	buf[b >> 3] = (byte_b & (~mask_b)) | ((byte_a & mask_a)? mask_b : 0);
}

ECMBOOL
get_license_value(char *license_key, int *product_arch,
		  int *expire_time, int *node_limit)
{
	product_key_t	pk;

	if (license_key_to_product_key(license_key, (char *)&pk))
		return ECM_FALSE;

	/* unsuffle bits */

#undef SWAP_BIT 
#define SWAP_BIT(aB, ab, bB, bb)  \
	swap_bit((char *)&pk, ((aB) << 3) + (ab), ((bB) << 3) + (bb))

	SWAP_BIT(0, 1, 12, 7);
	SWAP_BIT(0, 3, 12, 4);
	SWAP_BIT(0, 5, 12, 2);
	SWAP_BIT(0, 6, 13, 0);
	SWAP_BIT(1, 0, 13, 3);
	SWAP_BIT(1, 3, 9,  2);
	SWAP_BIT(1, 6, 10, 5);
	SWAP_BIT(2, 1, 12, 1);
	SWAP_BIT(2, 3, 12, 5);
	SWAP_BIT(2, 4, 12, 6);
	SWAP_BIT(2, 7, 12, 3);
	SWAP_BIT(3, 0, 13, 1);
	SWAP_BIT(3, 2, 13, 2);
	SWAP_BIT(4, 5, 13, 7);
	SWAP_BIT(4, 6, 13, 6);
	SWAP_BIT(5, 7, 13, 5);

	/* change to host byte order */
	pk.expire_time = ecm_ntohl(pk.expire_time);
	pk.node_limit = ecm_ntohs(pk.node_limit);
	pk.random_key = ecm_ntohs(pk.random_key);

	/* unshift fields */
	pk.node_limit = SHIFT_SHORT(pk.node_limit, 16 - NODE_LIMIT_SHIFT);
	pk.expire_time = SHIFT_INT(pk.expire_time, 32 - EXPIRE_TIME_SHIFT);

	if (pk.expire_time % 60 == 1)  /* unlimited time license */
		*expire_time = 0;
	else if (pk.expire_time % 60 == 0)  /* limited time license */
		*expire_time = pk.expire_time;
	else
		return ECM_FALSE;  /* invalid expire_time */

	*product_arch = pk.product_arch;
	*node_limit = pk.node_limit;

	return ECM_TRUE;
}

/*
 * External Interface For License Check -------------------------------------
 */ 

#ifndef KEYGEN
 
/*
 * EXTERNAL: license_check_node() ----------------------------------------
 */

int
license_check_node(int node_num)
{
	if (license_node != 0 && node_num > license_node) {
		ECMERR("LICENSE ERROR: node number %d exceeds %d", node_num, license_node);
		return -1;
	}
	else
		return 0;
}

/*
 * EXTERNAL: license_check_level() --------------------------------------
 */

int
license_get_level(void)
{
	return license_level; 
}

static char *
read_key(void)
{
	int	fd;
	char	buf[36];
	char	*keypath, *keybuf = NULL;
	ecm_size_t	size;

	keypath = ecm_gen_path(edbd_clxdir, "etc", LICENSE_KEY_FILE, NULL);
	if ((fd = ecm_open(keypath, ECM_O_RDONLY)) < 0) {
		ECMERR("LICENSE ERROR: cannot open %s", keypath);
		goto out;;
	}
	size = 35;
	if (!ecm_read(fd, buf, &size) || size != 35)
		goto out;
	buf[35] = '\0';
	keybuf = ecm_strdup(buf);
out:
	if (fd >= 0)
		ecm_close(fd);
	ecm_free(keypath);
	return keybuf;
}

ECMBOOL
license_check(void)
{
	char	*license_key, arch_str[64], expire_time_str[64], node_limit_str[64];
	int	arch, expire_time, node_limit;
	ECMBOOL	res = ECM_FALSE;

	if ((license_key = read_key()) == NULL)
		return ECM_FALSE;

	if (!get_license_value(license_key, &arch, &expire_time, &node_limit)) {
		ECMERR("LICENSE ERROR: invalid license key");
		goto out;
	}

	/* TODO: Architecture Platform Check Later */

	/* set license parameters */ 
	license_expire = expire_time;
	license_node = node_limit;
	license_level = get_license_level(license_key);

	/* generate license condition output */
	get_license_arch_str(arch, arch_str); 

	if (license_expire == 0)
		strcpy(expire_time_str, "unlimited");
	else {
		time_t tc;
		tc = (time_t)license_expire;
		sprintf(expire_time_str, ctime(&tc));
		expire_time_str[strlen(expire_time_str) - 1] = 0;
	}
	if (license_node == 0)
		strcpy(node_limit_str, "unlimited");
	else
		sprintf(node_limit_str, "%d", license_node);

	ECMVERBOSE("Licensed for %s level, %s arch, %s nodes, until %s",
		   get_license_level_str(license_level), arch_str,
		   node_limit_str, expire_time_str);

	/* TODO: License time expiration */
	res = ECM_TRUE;
out:
	ecm_free(license_key);
	return res;
}

#endif  /* !KEYGEN */

